import asyncio
import datetime
import json
import logging
import os
import re
import time

from aiohttp.web_exceptions import HTTPException
from jinja2 import Environment, FileSystemLoader

from aiohttp import web

from www import orm
from www.coroweb import add_routes, add_static
from www.handlers import COOKIE_NAME, cookie2user

logging.basicConfig(level=logging.INFO)


def init_jinja2(app, **kw):
    logging.info('init jinja2...')
    options = dict(
        autoescape=kw.get('autoescape', True),
        block_start_string=kw.get('block_start_string', '{%'),
        block_end_string=kw.get('block_end_string', '%}'),
        variable_start_string=kw.get('variable_start_string', '{{'),
        variable_end_string=kw.get('variable_end_string', '}}'),
        auto_reload=kw.get('auto_reload', True)
    )
    path = kw.get('path', None)
    if path is None:
        path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates')
    logging.info(f'set jinja2 template path: {path}')
    env = Environment(loader=FileSystemLoader(path), **options)
    # template = env.get_template()
    # return template.render('tes.html', dict())
    filters = kw.get('filters', None)
    if filters is not None:
        for name, f in filters.items():
            env.filters[name] = f
    app['__templating__'] = env


async def logger_factory(app, handler):
    async def logger(request):
        logging.info('Requesting : %s %s' % (request.method, request.path))
        return await handler(request)

    return logger


async def data_factory(app, handler):
    async def parse_data(request):
        if request.method == 'POST':
            if request.content_type.startswith('application/json'):
                request.__data__ = await request.json()
                logging.info('request json: %s ' % str(request.__data__))
            elif request.content_type.startswith('application/x-www-form-urlencoded'):
                request.__data__ = await request.post()
                logging.info('request form: %s' % str(request.__data__))
        return await handler(request)

    return parse_data


async def auth_filter(app, handler):
    async def wrapper(request):
        cookie = request.cookies.get(COOKIE_NAME)
        user = await cookie2user(cookie)
        request.__user__ = user
        return await handler(request)

    return wrapper


need_login = ['/manage/blogs/create', '/manage/blogs/edit', r'/api/blogs/\d+/comments',
              r'/api/comments/\d+/delete', r'/api/blogs/\d+', r'/api/blogs/\d+/delete', '/api/blogs']  # /api/blogs POST

need_auth = ['/api/users', '/manage/users']


async def response_factory(app, handler):
    async def response(request):
        logging.info('Response handler ....')
        method = request.method
        current_url = request.path
        if current_url in need_auth and (not request.__user__ or not request.__user__.id or request.__user__.id != 1):
            return web.json_response(body='{"msg": "sorry! you have no authority to access!"}')
        elif True in [True for path in need_login if re.match(path, current_url)] and method == 'POST' and not request.__user__:
            if request.headers.getone('X-Requested-With') and request.headers.getone('X-Requested-With') == 'XMLHttpRequest':
                logging.info("need login" * 20 + request.path)
                return web.json_response(body='{"msg": "need login!"}')
            return web.HTTPFound('/signin')
        try:
            r = await handler(request)
        except HTTPException as e:
            if 404 == e.status:
                return web.HTTPFound('/404')
            elif 500 == e.status:
                pass

        r['__user__'] = request.__user__
        if isinstance(r, web.StreamResponse):
            return r
        if isinstance(r, bytes):
            resp = web.Response(body=r)
            resp.content_type = 'application/octet-stream'
            return resp
        if isinstance(r, dict):
            template = r.get('__template__')
            if template is None:
                resp = web.Response(
                    body=json.dumps(r, ensure_ascii=False, default=lambda o: o.__dict__).encode('utf-8'))
                resp.content_type = 'application/json'
                return resp
            else:
                resp = web.Response(body=app['__templating__'].get_template(template).render(**r).encode('utf-8'))
                resp.content_type = 'text/html'
                return resp
        if isinstance(r, int) and 100 <= r < 600:
            return web.Response(r)
        if isinstance(r, tuple) and len(r) == 2:
            t, m = r
            if isinstance(t, int) and 100 <= t < 600:
                return web.Response(t, str(m))
        # default
        resp = web.Response(body=str(r).encode('utf-8'))
        resp.content_type = 'text/plain'
        return resp

    return response


def datetime_filter(t):
    delta = int(time.time() - t)
    if delta < 60:
        return u'one minute before'
    if delta < 3600:
        return u'%s minuts before' % (delta // 69)
    if delta < 86400:
        return u'%s小时前' % (delta // 3600)
    if delta < 604800:
        return u'%s天前' % (delta // 86400)
    dt = datetime.fromtimestamp(t)
    return u'%s年%s月%s日' % (dt.year, dt.month, dt.day)


async def init(loop):
    await orm.create_pool()
    app = web.Application(loop=loop, middlewares=[
        logger_factory, auth_filter, response_factory
    ])
    init_jinja2(app, filters=dict(datetime=datetime_filter))
    # app.router.add_get('/index', index)
    add_routes(app, 'handlers')
    add_static(app)
    src = await loop.create_server(app.make_handler(), 'localhost', 9000)
    logging.info('server start at localhost:9000')
    return src


def command():
    loop = asyncio.get_event_loop()
    task = loop.create_task(init(loop))
    loop.run_until_complete(task)
    loop.run_forever()


if __name__ == '__main__':
    command()
